package main

import (
	"bufio"
	"fmt"
	"io"
	"math/rand"
	"os"
	"strconv"
	"strings"
	"time"
)

var UnitOfSize = map[byte]uint64{
	'K': 1_024,
	'M': 1_048_576,
	'G': 1_073_741_824,
}

var Stdin = bufio.NewReader(os.Stdin)

const (
	textBlack = iota + 30
	textRed
	textGreen
	textYellow
	textBlue
	textPurple
	textCyan
	textWhite
)

const (
	bgBlack = iota + 40
	bgRed
	bgGreen
	bgYellow
	bgBlue
	bgPurple
	bgCyan
	bgWhite
)

func main() {
	var s string
	for true {
		fmt.Println("请选择操作[ 1)写入测试; 2)测试丢失]")
		_, err := Scanln(&s)
		if err != nil {
			PrintError(err.Error())
			return
		}
		if s == "1" {
			WriteTest()
		} else if s == "2" {
			LoseTest()
		} else {
			continue
		}
	}
}

func Scanln(str *string) (int, error) {
	s, _, err := Stdin.ReadLine()
	*str = string(s)
	if len(s) == 0 {
		err = fmt.Errorf("空输入")
	}
	return len(s), err
}

func PrintByColor(color int, str string) {
	fmt.Printf("\x1b[0;%dm%s\x1b[0m\n", color, str)
}

func PrintError(str string) {
	PrintByColor(textRed, str)
}

func PrintInfo(str string) {
	PrintByColor(textGreen, str)
}

func PrintTestPass() {
	// 打印PASS
	PrintInfo(`
########     ########     ########     ########
#      #     #      #     #            #       
#      #     #      #     #            #       
#      #     #      #     #            #       
#      #     #      #     #            #       
#      #     #      #     #            #       
########     ########     ########     ########
#            #      #            #            #
#            #      #            #            #
#            #      #            #            #
#            #      #            #            #
#            #      #            #            #
#            #      #     ########     ########`)
}

func PrintTestError() {
	// 打印ERROR
	PrintError(`
########     ########     ########     ########     ########
#            #      #     #      #     #      #     #      #
#            #      #     #      #     #      #     #      #
#            #      #     #      #     #      #     #      #
#            #      #     #      #     #      #     #      #
#            #      #     #      #     #      #     #      #
########     ########     ########     #      #     ########
#            #   #        #   #        #      #     #   #   
#            #      #     #      #     #      #     #      #
#            #      #     #      #     #      #     #      #
#            #      #     #      #     #      #     #      #
#            #      #     #      #     #      #     #      #
########     #      #     #      #     ########     #      #`)
}

func WriteTestToFile(pathList []string, size uint64) {
	var files = make([]*os.File, 0)
	defer func() {
		for i := 0; i < len(files); i++ {
			files[i].Close()
		}
	}()
	charList := [36]byte{'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'K', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
		'1', '2', '3', '4', '5', '6', '7', '8', '9', '0'}
	r := rand.New(rand.NewSource(time.Now().Unix()))
	for i := 0; i < len(pathList); i++ {
		// 防止两边有空格
		pathList[i] = strings.TrimSpace(pathList[i])
		// 防止pathList中文件名有双引号
		if len(pathList[i]) > 2 && pathList[i][0] == '"' && pathList[i][len(pathList[i])-1] == '"' {
			pathList[i] = pathList[i][1 : len(pathList[i])-1]
		}
	}
	for i := 0; i < len(pathList); i++ {
		f, err := os.Create(pathList[i])
		if err != nil {
			PrintError(err.Error())
			return
		}
		files = append(files, f)
	}
	var i uint64
	w := make([]byte, 0, 1024)
	// 让i从1开始，size+1结束，是为了保证写入的第一个字符不是“\n”
	for i = 1; i < size+1; i++ {
		if i%1024 != 0 {
			w = append(w, charList[r.Intn(35)])
		} else {
			w = append(w, '\n')
			for k := 0; k < len(pathList); k++ {
				_, err := files[k].Write(w)
				if err != nil {
					PrintError(err.Error())
					return
				}
			}
			w = make([]byte, 0, 1024)
		}
	}
	PrintInfo("写入完成")
}

func WriteTest() {
	var s string
	fmt.Println("是否写入测试[y/n]")
	_, err := Scanln(&s)
	if err != nil {
		PrintError(err.Error())
		return
	}
	if s == "y" || s == "Y" {
		var pathList = make([]string, 0)
		fmt.Println("写入位置1:")
		_, err = Scanln(&s)
		if err != nil {
			PrintError(err.Error())
			return
		}
		pathList = append(pathList, s)
		fmt.Println("写入位置2:")
		_, err = Scanln(&s)
		if err != nil {
			PrintError(err.Error())
			return
		}
		pathList = append(pathList, s)
		for i := 3; true; i++ {
			fmt.Println("是否有位置", i, "[y/n]")
			_, err = Scanln(&s)
			if err != nil {
				PrintError(err.Error())
				return
			}
			if s == "y" || s == "Y" {
				fmt.Println(fmt.Sprintf("写入位置%d:", i))
				_, err = Scanln(&s)
				if err != nil {
					PrintError(err.Error())
					return
				}
				pathList = append(pathList, s)
			} else if s == "n" || s == "N" {
				break
			} else {
				i -= 1
				continue
			}
		}
	InputTestSizeAgain:
		fmt.Println("测试大小:")
		_, err = Scanln(&s)
		if err != nil {
			PrintError(err.Error())
			return
		}
		numberOfTestSize, err := strconv.ParseUint(s[:len(s)-1], 10, 64)
		if err != nil {
			goto InputTestSizeAgain
		}
		unixOfTestSize, ok := UnitOfSize[s[len(s)-1]]
		if !ok {
			goto InputTestSizeAgain
		}
		size := numberOfTestSize * unixOfTestSize
		WriteTestToFile(pathList, size)
	} else if s == "n" || s == "N" {
		return
	} else {
		return
	}
}

func TestLoseFromFile(pathList []string) {
	var files = make([]*os.File, 0)
	defer func() {
		for i := 0; i < len(files); i++ {
			files[i].Close()
		}
	}()
	for i := 0; i < len(pathList); i++ {
		// 防止两边有空格
		pathList[i] = strings.TrimSpace(pathList[i])
		// 防止pathList中文件名有双引号
		if len(pathList[i]) > 2 && pathList[i][0] == '"' && pathList[i][len(pathList[i])-1] == '"' {
			pathList[i] = pathList[i][1 : len(pathList[i])-1]
		}
	}
	for i := 0; i < len(pathList); i++ {
		f, err := os.Open(pathList[i])
		if err != nil {
			PrintError(err.Error())
			return
		}
		files = append(files, f)
	}
	s, err := os.Stat(pathList[0])
	if err != nil {
		PrintError(err.Error())
		os.Exit(1)
	}
	size := s.Size()
	for j := 0; j < len(pathList); j++ {
		s, err = os.Stat(pathList[j])
		if err != nil {
			PrintError(err.Error())
			os.Exit(1)
		}
		if size != s.Size() {
			PrintTestError()
			PrintError("大小不相等")
			return
		}
		size = s.Size()
	}
	var errByte int64
readFinish:
	for {
		rs := make([][]byte, len(files))
		for i := 0; i < len(files); i++ {
			r := make([]byte, 1024)
			_, err := files[i].Read(r)
			if err == io.EOF {
				break readFinish
			} else if err != nil {
				PrintError(err.Error())
				return
			}
			rs[i] = r
		}
		for i := 0; i < len(rs[0]); i++ {
			var byt byte
			byt = rs[0][i]
			for j := 0; j < len(rs); j++ {
				if byt != rs[j][i] {
					errByte += 1
					break
				}
				byt = rs[j][i]
			}
		}
	}
	s, err = os.Stat(pathList[0])
	if err != nil {
		PrintError(err.Error())
		os.Exit(1)
	}
	size = s.Size()
	lose := (float64(errByte) / float64(size)) * 100
	var color int
	if errByte == 0 {
		PrintTestPass()
		color = textGreen
	} else if lose < 0.5 {
		PrintTestError()
		color = textYellow
	} else {
		PrintTestError()
		color = textRed
	}
	if size < int64(UnitOfSize['K']) {
		PrintByColor(color, "文件大小:"+strconv.FormatInt(size, 10)+"B")
	} else if size < int64(UnitOfSize['M']) {
		PrintByColor(color, "文件大小:"+strconv.FormatInt(size/int64(UnitOfSize['K']), 10)+"K")
	} else if size < int64(UnitOfSize['G']) {
		PrintByColor(color, "文件大小:"+strconv.FormatInt(size/int64(UnitOfSize['M']), 10)+"M")
	} else if size >= int64(UnitOfSize['G']) {
		PrintByColor(color, "文件大小:"+strconv.FormatInt(size/int64(UnitOfSize['G']), 10)+"G")
	}
	PrintByColor(color, "丢失率:"+strconv.FormatFloat(lose, 'f', 6, 64)+"%")
	PrintByColor(color, "共丢失字节:"+strconv.FormatInt(errByte, 10)+"字节")
}

func LoseTest() {
	var s string
	fmt.Println("是否测试丢失[y/n]")
	_, err := Scanln(&s)
	if err != nil {
		PrintError(err.Error())
		return
	}
	if s == "y" || s == "Y" {
		var pathList = make([]string, 0)
		fmt.Println("测试位置1:")
		_, err = Scanln(&s)
		if err != nil {
			PrintError(err.Error())
			return
		}
		pathList = append(pathList, s)
		fmt.Println("测试位置2:")
		_, err = Scanln(&s)
		if err != nil {
			PrintError(err.Error())
			return
		}
		pathList = append(pathList, s)
		TestLoseFromFile(pathList)
	} else if s == "n" || s == "N" {
		return
	} else {
		return
	}
}
